#include "EPhActor.h"
#include "EShape.h"
#include "EPhMaterial.h"
#include "EPhysics.h"
#include "PhysX/eStream.h"
#include <NxCooking.h>

#include "eCPP.h"
namespace engine
{

//------------------------------------------------------------
EPhActorDesc::EPhActorDesc( )
{

}

//------------------------------------------------------------
EPhActorDesc::~EPhActorDesc()
{
  for( vector<NxShapeDesc*>::iterator it=m_vecNxShapes.begin();
    it != m_vecNxShapes.end(); ++it )
  {
    delete *it;
  }
  m_vecNxShapes.clear();

  cASSERT( g_pPhysics != NULL );
  for( vector<NxTriangleMesh*>::iterator it=m_vecNxTriangleMeshes.begin();
    it != m_vecNxTriangleMeshes.end(); ++it )
  {
    g_pPhysics->getNxSDK().releaseTriangleMesh( *(*it) );
  }
  m_vecNxTriangleMeshes.clear();
}

//------------------------------------------------------------
bool EPhActorDesc::addShape(const EPhShapeDesc& desc )
{
  cASSERT( desc.m_Shape.getType() != EShape::UNKNOWN_SHAPE_TYPE );

  switch( desc.m_Shape.getType() )
  {
  case EShape::BOX_SHAPE:
    addBoxShape( desc );
    break;
  case EShape::SPHERE_SHAPE:
    addSphereShape( desc );
    break;
  case EShape::CAPSULE_SHAPE:
    addCapsuleShape( desc );
    break;
  case EShape::PLANE_SHAPE:
    addPlaneShape( desc );
    break;
  case EShape::TRIANGLE_MESH_SHAPE:
    addTriangleMeshShape( desc );
    break;
  default:
    cERROR("this shape type is not handled by physics actors");
    break;
  }

  return true;
}

//------------------------------------------------------------
bool EPhActorDesc::addPlaneShape( const EPhShapeDesc& desc )
{
  const EPlaneShape& shape = static_cast<const EPlaneShape&>( desc.m_Shape );
  NxPlaneShapeDesc *pShapeDesc = new NxPlaneShapeDesc();
  m_vecNxShapes.push_back( pShapeDesc );
  pShapeDesc->normal          = shape.getNormal().getNxVec3();
  pShapeDesc->d               = shape.getD();
  pShapeDesc->group           = eGET_NX_COLLISION_GROUP( desc.m_eCollisionGroup );
  pShapeDesc->shapeFlags      |= desc.m_flagFlags;
  if( desc.m_pMaterial != NULL )
    pShapeDesc->materialIndex   = desc.m_pMaterial->getMaterialIndex();

  cTEST( pShapeDesc->isValid() );
  m_NxActorDesc.shapes.pushBack( pShapeDesc );

  return true;
}

//------------------------------------------------------------
bool EPhActorDesc::addBoxShape( const EPhShapeDesc& desc )
{
  const EBoxShape& shape = static_cast<const EBoxShape&>( desc.m_Shape );
  NxBoxShapeDesc *pShapeDesc = new NxBoxShapeDesc();
  m_vecNxShapes.push_back( pShapeDesc );
  pShapeDesc->dimensions      = shape.getDimensions().getNxVec3();
  pShapeDesc->group           = eGET_NX_COLLISION_GROUP( desc.m_eCollisionGroup );
  pShapeDesc->shapeFlags      |= desc.m_flagFlags;
  if( desc.m_pMaterial != NULL )
    pShapeDesc->materialIndex   = desc.m_pMaterial->getMaterialIndex();

  cTEST( pShapeDesc->isValid() );
  m_NxActorDesc.shapes.pushBack( pShapeDesc );

  return true;
}

//------------------------------------------------------------
bool EPhActorDesc::addCapsuleShape( const EPhShapeDesc& desc )
{
  const ECapsuleShape& shape = static_cast<const ECapsuleShape&>( desc.m_Shape );
  NxCapsuleShapeDesc* pShapeDesc = new NxCapsuleShapeDesc();
  m_vecNxShapes.push_back( pShapeDesc );
  pShapeDesc->radius    = shape.getRadius();
  pShapeDesc->height    = shape.getHeight();
  pShapeDesc->group     = eGET_NX_COLLISION_GROUP( desc.m_eCollisionGroup );
  pShapeDesc->shapeFlags |= desc.m_flagFlags;
  if( desc.m_pMaterial != NULL )
    pShapeDesc->materialIndex   = desc.m_pMaterial->getMaterialIndex();

  cTEST( pShapeDesc->isValid() );
  m_NxActorDesc.shapes.pushBack( pShapeDesc );

  return true;
}

//------------------------------------------------------------
bool EPhActorDesc::addSphereShape( const EPhShapeDesc& desc )
{
  const ESphereShape& shape = static_cast<const ESphereShape&>( desc.m_Shape );
  NxSphereShapeDesc* pShapeDesc = new NxSphereShapeDesc();
  m_vecNxShapes.push_back( pShapeDesc );
  pShapeDesc->radius    = shape.getRadius();
  pShapeDesc->group     = eGET_NX_COLLISION_GROUP( desc.m_eCollisionGroup );
  pShapeDesc->shapeFlags |= desc.m_flagFlags;
  if( desc.m_pMaterial != NULL )
    pShapeDesc->materialIndex   = desc.m_pMaterial->getMaterialIndex();

  cTEST( pShapeDesc->isValid() );
  m_NxActorDesc.shapes.pushBack( pShapeDesc );

  return true;
}

//------------------------------------------------------------
bool EPhActorDesc::addTriangleMeshShape( const EPhShapeDesc& desc )
{
  const ETriangleMeshShape& shape = static_cast<const ETriangleMeshShape&>( desc.m_Shape );
  const MeshPtr& ogreMeshPtr = shape.getOgreMeshPtr();
  cTEST( ogreMeshPtr->isLoaded() );
  ushort usNumSubMeshes = ogreMeshPtr->getNumSubMeshes();

  vector<Vector4> vecVert;
  vector<NxU32>   vecInd;

  /// \todo tmp
  EVector vScale( 1.f );

  size_t        iIndOffset    = 0;
  unsigned long ulVertOffset  = 0;

  for(ushort s = 0; s < usNumSubMeshes; s++)
  {
    SubMesh *pSubMesh = ogreMeshPtr->getSubMesh(s);

    // ogre has two types of meshes: meshes with all vertices shared,
    // and meshes with vertices associated with each submesh.
    // i'm not sure if i'm doing this right. (see TODO below)
    VertexData *pVertexData = ogreMeshPtr->sharedVertexData;
    if( !pSubMesh->useSharedVertices )
      pVertexData = pSubMesh->vertexData;

    // figure out the offset of the position data within the vertex buffer
    VertexDeclaration *pVertexDecl  = pVertexData->vertexDeclaration;
    size_t  iNbVertexElements       = pVertexDecl->getElementCount();
    size_t  iVertexSize             = pVertexDecl->getVertexSize(0) / sizeof(Real);
    size_t  iVertexPosOffset        = 0;

    for(size_t i = 0; i < iNbVertexElements; i++)
    {
      if(pVertexDecl->getElement((ushort)i)->getSemantic() == VES_POSITION)
        iVertexPosOffset = pVertexDecl->getElement((ushort)i)->getOffset();
    }

    // store vertex data in pVertex
    VertexBufferBinding *pVertexBinding     = pVertexData->vertexBufferBinding;
    HardwareVertexBufferSharedPtr pVertBuf  = pVertexBinding->getBuffer(0);
    size_t iNumVerts  = pVertBuf->getNumVertices();
    Real  *pVertex    = static_cast<Real*>(pVertBuf->lock(HardwareBuffer::HBL_DISCARD));

    cASSERT( pVertex != NULL );

    for(size_t i = 0; i < iNumVerts; i++)
    {
      Real *piVertex   = pVertex + iVertexPosOffset;
      Real *pX         = (piVertex);
      Real *pY         = (piVertex + 1);
      Real *pZ         = (piVertex + 2);

      Vector4 v(
        *pX * vScale.x, 
        *pY * vScale.y , 
        *pZ * vScale.z, 
        (float) ulVertOffset );
      vecVert.push_back(v);

      pVertex   += iVertexSize;
      ulVertOffset++;
    }

    pVertBuf->unlock();

    // store index data in pIndex
    IndexData                       *pIndexData  = pSubMesh->indexData;
    HardwareIndexBufferSharedPtr    pIndBuf     = pIndexData->indexBuffer;
    HardwareIndexBuffer::IndexType  indType     = pIndBuf->getType();
    size_t                          iNbIndices  = pIndexData->indexCount;

    switch( indType )
    {
    case HardwareIndexBuffer::IT_16BIT:
      {
        unsigned short *pIndex = static_cast<ushort *>(pIndBuf->lock(HardwareBuffer::HBL_DISCARD));

        cASSERT( pIndex != NULL );

        for(size_t i = 0; i < iNbIndices; i++)
          vecInd.push_back(pIndex[i] + iIndOffset);

        pIndBuf->unlock();
      }
      break;

    case HardwareIndexBuffer::IT_32BIT:
      {
        // BUG: I'm not totally sure this code is correct.. beware!

        unsigned long *pIndex = static_cast<ulong *>(pIndBuf->lock(HardwareBuffer::HBL_DISCARD));

        cASSERT( pIndex != NULL );

        for(size_t i = 0; i < iNbIndices; i++)
          vecInd.push_back(pIndex[i] + iIndOffset);

        pIndBuf->unlock();

      }
      break;

    default: break;
    }

    iIndOffset = vecVert.size();

  }
  /*ulong k = 0;
  for(vector<Vector4>::iterator i = vecVert.begin(); i != vecVert.end(); )
  {
    for(vector<Vector4>::iterator j = i + 1; j != vecVert.end(); )
    {
      //printOgre("Checking", *j);
      if(i->x == j->x && i->y == j->y && i->z == j->z)   // j->z: jay to the z! jigga!
      {
        //printOgre("Removing duplicate vertex", *j);

        // step through index list and remove references to this vertex
        for(ulong t = 0; t < vecInd.size(); t++)
        {
          if(vecInd[t] == (ulong) j->w)
            vecInd[t] = k;
        }

        vecVert.erase(j);
      }
      else
        j++;
    }

    // step through index list and update old references to this vertex
    // (this particular vertex may have moved)
    for(ulong t = 0; t < vecInd.size(); t++)
    {
      if(vecInd[t] == (ulong) i->w)
        vecInd[t] = k;
    }

    k++;

    if(i != vecVert.end())
      i++;
  }*/

  float *pVerts = new float[ vecVert.size() * 3 ];
  for(ulong i = 0; i < vecVert.size(); i++)
  {
    //NxVec3 v(vertlist[i].x,vertlist[i].y, vertlist[i].z);
    //printf("%d: %f%c", i/3, vertlist[i], (i+1) % 3 ? '\t' : '\n');
    //printNx(i, v);
    ulong ulInd = i*3;
    pVerts[ulInd]   = vecVert[i].x;
    pVerts[ulInd+1] = vecVert[i].y;
    pVerts[ulInd+2] = vecVert[i].z;

    //verts[i] = vertlist[i];
  }

  NxU32 *pIndices = new NxU32[vecInd.size()];
  for(ulong i = 0; i < vecInd.size(); i++)
  {
    //printf("%d%c", indlist[i], (i+1) % 3 ? '\t' : '\n');
    pIndices[i] = (NxU32)( vecInd[i] );
  }
  cASSERT( vecInd.size() % 3 == 0 );

  // Nx mesh desc
  NxTriangleMeshDesc nxMeshDesc;
  nxMeshDesc.numVertices				  = (NxU32)( vecVert.size() );
  nxMeshDesc.numTriangles				  = (NxU32)( vecInd.size() / 3 );
  nxMeshDesc.pointStrideBytes			= sizeof(NxVec3);
  nxMeshDesc.triangleStrideBytes  = 3 * sizeof(NxU32);
  nxMeshDesc.points					      = pVerts;
  nxMeshDesc.triangles					  = pIndices;
  nxMeshDesc.flags						    = 0;

  EMemoryWriteBuffer writeBuffer;
  cTEST( NxCookTriangleMesh(nxMeshDesc, writeBuffer) );

  cASSERT( g_pPhysics != NULL );
  NxTriangleMesh* pNxMesh = g_pPhysics->getNxSDK().createTriangleMesh( EMemoryReadBuffer(writeBuffer.data) );
  m_vecNxTriangleMeshes.push_back( pNxMesh );
  cTEST( pNxMesh != NULL );

  delete pVerts;      pVerts = NULL;
  delete pIndices;    pIndices = NULL;

  // Nx shape desc
  NxTriangleMeshShapeDesc* pShapeDesc  = new NxTriangleMeshShapeDesc();
  m_vecNxShapes.push_back( pShapeDesc );
  pShapeDesc->meshData          = pNxMesh;
  pShapeDesc->group             = eGET_NX_COLLISION_GROUP( desc.m_eCollisionGroup );
  pShapeDesc->shapeFlags        |= desc.m_flagFlags;
  if( desc.m_pMaterial != NULL )
    pShapeDesc->materialIndex   = desc.m_pMaterial->getMaterialIndex();

  cTEST( pShapeDesc->isValid() );
  m_NxActorDesc.shapes.pushBack( pShapeDesc );
  return true;
}

//------------------------------------------------------------
const NxActorDesc& EPhActorDesc::getNxActorDesc() const
{
  return m_NxActorDesc;
}

//------------------------------------------------------------
bool EPhActorDesc::isValid() const
{
  cTEST( m_vecNxShapes.size() > 0 );
  return true;
}

//------------------------------------------------------------
EPhActor::EPhActor(NxActor* pNxActor ) :
m_pNxActor( pNxActor )
{

}

//------------------------------------------------------------
void EPhActor::setPosition(const EVector& vPos )
{
  cASSERT( m_pNxActor != NULL );
  m_pNxActor->setGlobalPosition( vPos.getNxVec3() );
}


} // end namespace
